/*
 * Copyright © OpenAtom Foundation.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *     http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package io.iec.edp.caf.databaseobject.api.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.io.*;
import java.util.*;

/**
 * 数据库对象表实体
 * 因为使用的ObjectMapper序列化，存在一个bug，所有is开头的属性，必须使用JsonProperty标签且设置忽略大小写，否则会一直返回false
 *
 * @author liu_wei
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class DatabaseObjectTable extends DatabaseObjectTableCore implements Serializable {
    /**
     * 数据库对象表构造函数
     */
    public DatabaseObjectTable() {
        this(UUID.randomUUID().toString());
    }

    private DatabaseObjectTable(String dboId) {
        this.setId(dboId);
        this.setType(DatabaseObjectType.Table);
        this.setColumns(new ArrayList<>());
        this.setIndexes(new ArrayList<>());
        this.setPrimaryKey(new ArrayList<>());
        this.multiLanguageColumns = new ArrayList<>();
    }

    /**
     * 数据库对象实体构造方法
     *
     * @param code             编号
     * @param name             名称
     * @param isSynHis         是否同步创建历史表
     * @param isI18NObject     是否多语
     * @param isUsingTimeStamp 是否启用时间戳字段
     * @param isFiscalTable    是否为年度表
     * @param businessObjectId 业务对象字段ID
     */

    public DatabaseObjectTable(String code, String name, boolean isSynHis, boolean isI18NObject, boolean isUsingTimeStamp, boolean isFiscalTable, String businessObjectId) {
        this(code, name, isSynHis, isI18NObject, isUsingTimeStamp, isFiscalTable, businessObjectId, null);
    }


    public DatabaseObjectTable(String code, String name, boolean isSynHis, boolean isI18NObject, boolean isUsingTimeStamp, boolean isFiscalTable, String businessObjectId, String ID) {
        if (ID == null || ID.length() == 0) {
            this.setId(UUID.randomUUID().toString());
        } else {
            this.setId(ID);
        }
        this.setCode(code);
        this.setName(name);
        this.setSynHis(isSynHis);
        this.setI18NObject(isI18NObject);
        this.setType(DatabaseObjectType.Table);
        this.setUsingTimeStamp(isUsingTimeStamp);
        this.setFiscalTable(isFiscalTable);
        this.setBusinessObjectId(businessObjectId);
        this.setColumns(new ArrayList<>());
        this.setIndexes(new ArrayList<>());
        this.setPrimaryKey(new ArrayList<>());
        this.multiLanguageColumns = new ArrayList<>();
    }

    /**
     * 业务对象字段关联属性
     */
    private String businessObjectId;

    /**
     * 主键名
     */
    private String pkName;
    /**
     * 对象是否多语
     */
    @JsonProperty("isI18nObject")
    private boolean isI18NObject;
    /**
     * 是否年度表
     */
    @JsonProperty("isFiscalTable")
    private boolean isFiscalTable;
    /**
     * 是否同步历史表
     */
    @JsonProperty("isSynHis")
    private boolean isSynHis;
    /**
     * 是否启用时间戳字段
     */
    @JsonProperty("isUsingTimeStamp")
    private boolean isUsingTimeStamp;

    private List<String> primaryKey;

    /**
     * 数据库对象表主键列（冗余字段）
     */
    public final List<String> getPrimaryKey() {
        if (primaryKey == null) {
            primaryKey = new ArrayList<>();
        }
        return primaryKey;
    }

    public final void setPrimaryKey(List<String> value) {
        primaryKey = value;
    }

    private List<DatabaseObjectIndex> indexes;

    /**
     * 数据库对象索引列表
     */
    public final List<DatabaseObjectIndex> getIndexes() {
        if (this.indexes == null) {
            this.indexes = new ArrayList<>();
        }
        return indexes;
    }

    public final void setIndexes(List<DatabaseObjectIndex> value) {
        this.indexes = value;
    }

    private List<String> multiLanguageColumns;

    /**
     * 多语字段
     */
    public final List<String> getMultiLanguageColumns() {
        if (multiLanguageColumns == null) {
            multiLanguageColumns = new ArrayList<>();
        }
        return multiLanguageColumns;
    }

    @JsonIgnore
    private Map<String, String> multiLanguageColumnCodes;

    /**
     * 多语字段的编号
     */
    public final Map<String, String> getMultiLanguageColumnCodes() {
        if (multiLanguageColumns == null) {
            multiLanguageColumnCodes = new HashMap<>();
        } else {
            multiLanguageColumnCodes = new HashMap<>();
            for (String multiLanguageId : multiLanguageColumns) {
                DatabaseObjectColumn column = getColumnById(multiLanguageId);
                multiLanguageColumnCodes.put(multiLanguageId, column.getCode());
            }
        }
        return multiLanguageColumnCodes;
    }

    public final void setMultiLanguageColumns(List<String> value) {
        multiLanguageColumns = value;
    }

    /**
     * 租户标识字段
     */
    private DatabaseObjectColumn tenantIdentityColumn;

    /**
     * 数据库对象表名规则
     */
    private DBOTableNameRule dboTableNameRule;

    private boolean hasNameRule;

    /**
     * DBO是否包含表名规则的只读属性
     */
    public final boolean getHasNameRule() {
        return getDboTableNameRule() != null && getDboTableNameRule().getDimensionStrategyWithOrder() != null && !getDboTableNameRule().getDimensionStrategyWithOrder().isEmpty();
    }

    /**
     * 添加列
     *
     * @param id           ID
     * @param code         编号
     * @param name         名称
     * @param dataType     数据类型
     * @param length       长度
     * @param precision    精度
     * @param scale        小数位数
     * @param defaultValue 默认值
     * @param ifUnique     是否唯一
     * @param ifPK         是否主键
     * @param isNullable   是否可为空
     * @param isI18n       是否多语
     */
    public final void addColumn(String id, String code, String name, DataType dataType, int length, int precision, int scale, String defaultValue, boolean ifUnique, boolean ifPK, boolean isNullable, boolean isI18n) {
        DatabaseObjectColumn column = new DatabaseObjectColumn();
        column.setId(id);
        column.setCode(code);
        column.setName(name);
        column.setDataType(dataType);
        column.setLength(length);
        column.setPrecision(precision);
        column.setScale(scale);
        column.setDefaultValue(defaultValue);
        column.setUnique(ifUnique);
        column.setIfPrimaryKey(ifPK);
        column.setNullable(isNullable);
        column.setType(DatabaseObjectType.Column);

        if (ifPK) {
            this.getPrimaryKey().add(id);
            column.setNullable(false);
            column.setUnique(true);
        }
        if (isI18n) {
            column.setDataType(convertToI18NDataType(dataType, column.getCode()));
            this.getMultiLanguageColumns().add(id);
            this.setI18NObject(true);
        }
        this.getColumns().add(column);
    }

    /**
     * 修改列
     *
     * @param id           ID
     * @param name         字段名称
     * @param dataType     数据类型
     * @param length       长度
     * @param precision    精度
     * @param scale        小数位数
     * @param defaultValue 默认值
     * @param ifUnique     是否唯一
     * @param ifPK         是否主键
     * @param isNullable   是否可为空
     * @param isI18n       是否多语
     */
    public final void modifyColumn(String id, String name, DataType dataType, int length, int precision, int scale, String defaultValue, boolean ifUnique, boolean ifPK, boolean isNullable, boolean isI18n) {
        DatabaseObjectColumn column = getColumnById(id);
        if (column == null) {
            throw new RuntimeException(String.format("数据库对象表中不存在ID为%1$s的字段", id));
        }
        column.setId(id);
        if (isI18n || getMultiLanguageColumns().contains(id)) {
            dataType = convertToI18NDataType(dataType, column.getCode());
        }
        column.setName(name);
        column.setDataType(dataType);
        column.setLength(length);
        column.setPrecision(precision);
        column.setScale(scale);
        column.setDefaultValue(defaultValue);
        column.setUnique(ifUnique);
        column.setIfPrimaryKey(ifPK);
        column.setNullable(isNullable);
        if (ifPK) {
            column.setNullable(false);
            column.setUnique(true);

            if (!getPrimaryKey().contains(id)) {
                getPrimaryKey().add(id);
            }
        }
        if (!ifPK && this.getPrimaryKey().contains(id)) {
            this.getPrimaryKey().remove(id);
        }
        if (isI18n && !getMultiLanguageColumns().contains(id)) {
            this.getMultiLanguageColumns().add(id);
            this.setI18NObject(true);
        }
    }

    private DataType convertToI18NDataType(DataType dataType, String columnCode) {
        if (dataType == DataType.Varchar || dataType == DataType.NVarchar) {
            return DataType.NVarchar;
        }
        if (dataType == DataType.Char || dataType == DataType.NChar) {
            return DataType.NChar;
        }
        if (dataType == DataType.Clob || dataType == DataType.NClob) {
            return DataType.NClob;
        } else {
            throw new RuntimeException(String.format("当前字段数据类型不支持多语，字段编号为%1$s，当前数据类型为%2$s，请确认。", columnCode, dataType));
        }
    }


    /**
     * 添加租户标识字段
     */
    public final void addTenantIdColumn() {
        if (this.getColumnByCode("TenantID") == null) {
            this.setTenantIdentityColumn(new DatabaseObjectColumn());
            this.getTenantIdentityColumn().setId(UUID.randomUUID().toString());
            this.getTenantIdentityColumn().setCode("TenantID");
            this.getTenantIdentityColumn().setName("TenantID");
            this.getTenantIdentityColumn().setDataType(DataType.Varchar);
            this.getTenantIdentityColumn().setLength(36);
            this.getTenantIdentityColumn().setPrecision(0);
            this.getTenantIdentityColumn().setScale(0);
            this.getTenantIdentityColumn().setDefaultValue(null);
            this.getTenantIdentityColumn().setUnique(false);
            this.getTenantIdentityColumn().setIfPrimaryKey(false);
            this.getTenantIdentityColumn().setNullable(true);
        }
    }

    /**
     * 克隆
     *
     * @return 数据库对象表
     */
    @Override
    public DatabaseObjectTable clone() {
        DatabaseObjectTable table = new DatabaseObjectTable(this.getId());
        table.setCode(this.getCode());
        table.setName(this.getName());
        table.setPkName(getPkName());
        table.setBusinessObjectId(getBusinessObjectId());
        if (this.getHasNameRule())
            table.setDboTableNameRule((DBOTableNameRule) this.getDboTableNameRule().clone());
        table.setDescription(this.getDescription());
        table.setI18NObject(this.isI18NObject);
        table.setSynHis(this.isSynHis);
        table.setUsingTimeStamp(this.isUsingTimeStamp);
        if (this.tenantIdentityColumn != null) {
            table.setTenantIdentityColumn(this.tenantIdentityColumn.clone());
        }
        table.setType(this.getType());
        table.setVersion(this.getVersion());
        table.setFiscalTable(this.isFiscalTable);
        table.setLastModifiedTime(this.getLastModifiedTime());
        for (DatabaseObjectColumn column : this.getColumns()) {
            table.getColumns().add(column.clone());
        }
        for (DatabaseObjectIndex index : this.getIndexes()) {
            table.getIndexes().add(index.clone());
        }
        if (this.getMultiLanguageColumns() != null && this.getMultiLanguageColumns().size() > 0) {
            table.getMultiLanguageColumns().addAll(this.getMultiLanguageColumns());
        }
        if (this.getPrimaryKey() != null && this.getPrimaryKey().size() > 0) {
            table.getPrimaryKey().addAll(this.getPrimaryKey());
        }
        return table;
    }

    /**
     * 根据字段ID获取字段
     *
     * @param columnId 字段ID
     * @return 字段实体
     */
    @Override
    public final DatabaseObjectColumn getColumnById(String columnId) {
        Optional<DatabaseObjectColumn> columnsOpt = this.getColumns().stream().filter((item) -> item.getId().equals(columnId)).findFirst();
        return columnsOpt.orElse(null);
    }

    /**
     * 根据字段编号获取字段
     *
     * @param columnCode 字段编号
     * @return 字段实体
     */
    @Override
    public final DatabaseObjectColumn getColumnByCode(String columnCode) {
        Optional<DatabaseObjectColumn> columnsOpt = this.getColumns().stream().filter((item) -> item.getCode().toLowerCase().equals(columnCode.toLowerCase())).findFirst();
        return columnsOpt.orElse(null);
    }
}
